/*
 * Configuration.java
 *
 * Created on June 18, 2007, 3:01 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.xeerkat.agent.tools;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Location;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.infoset.xml.util.WriterItemDestination;
import org.xeerkat.agent.services.Service;
import org.xeerkat.agent.services.ServiceRegistry;
import org.xeerkat.xmpp.ID;

/**
 *
 * @author alex
 */
public class Configuration
{
   public static final URI NAMESPACE = URI.create("http://www.xeerkat.org/Vocabulary/Agent/Configuration/2007/1/0");
   static final Name SERVER = Name.create(NAMESPACE,"server");
   static final Name BROKER = Name.create(NAMESPACE,"broker");
   static final Name AGENT = Name.create(NAMESPACE,"agent");
   static final Name MANAGER = Name.create(NAMESPACE,"manager");
   static final Name ALLOW_FROM = Name.create(NAMESPACE,"allow-from");
   static final Name ALLOW_DOMAIN = Name.create(NAMESPACE,"allow-domain");
   static final Name INTERFACE = Name.create(NAMESPACE,"interface");
   static final Name HOST = Name.create(NAMESPACE,"host");
   static final Name SERVICES = Name.create(NAMESPACE,"services");
   static final Name KEYSTORE = Name.create(NAMESPACE,"keystore");
   static final Name UPGRADE = Name.create(NAMESPACE,"upgrade");
   static final Name FROM = Name.create(NAMESPACE,"from");
   static final Name DOMAIN = Name.create(NAMESPACE,"domain");
   static final Name FORWARD = Name.create(NAMESPACE,"forward");

   
   static public class Interface {
      String addr;
      int port;
      boolean secure;
      
      public Interface(String addr,int port,boolean secure)
      {
         this.addr = addr;
         this.port = port;
         this.secure = secure;
      }
      
      public String getAddress() {
         return addr;
      }
      
      public int getPort() {
         return port;
      }
      
      public boolean isSecure() {
         return secure;
      }
      
   }
   
   static public class Host {
      String name;
      String address;
      int port;
      
      public Host(String name)
      {
         this(name,null,-1);
      }
      
      public Host(String name,String address,int port)
      {
         this.name = name;
         this.address = address;
         this.port = port;
      }
      
      public String getName() {
         return name;
      }
      
      public String getAddress() {
         return address;
      }
      
      public int getPort() {
         return port;
      }

   }
   
   public class Agent {
      ID id;
      List<ID> brokers;
      List<Host> hosts;
      List<ServiceRegistry> registries;
      ServiceRegistry local;
      String forwardTarget;
      boolean builtin;
      
      public Agent(ID id) {
         this.id = id;
         this.brokers = new ArrayList<ID>();
         this.hosts = new ArrayList<Host>();
         this.registries = new ArrayList<ServiceRegistry>();
         this.local = new ServiceRegistry();
         this.registries.add(local);
         this.builtin = true;
      }
      
      public ID getId() {
         return id;
      }
      
      public List<ID> getBrokers() {
         return brokers;
      }

      public List<Host> getHosts() {
         return hosts;
      }

      public List<ServiceRegistry> getServiceRegistries() {
         return registries;
      }
      
      public ServiceRegistry getLocalServices() {
         return local;
      }

      public String getForwardTarget()
      {
         return forwardTarget;
      }
      
      public void setForwardTarget(String path)
      {
         forwardTarget = path;
      }

      public boolean allowBuiltin() {
         return builtin;
      }

      public void setAllowBuiltin(boolean flag) {
         this.builtin = flag;
      }

      public Configuration getConfiguration() {
         return Configuration.this;
      }
   }
   
   URI location;
   List<Interface> interfaces;
   List<Host> hosts;
   List<ServiceRegistry> registries;
   Map<ID,Agent> agents;
   File keyStorePath;
   String keyStorePassword;
   String username;
   String password;
   Set<ID> allowUpgradeFrom;
   Set<String> allowArchiveDomain;
   
   /** Creates a new instance of Configuration */
   public Configuration()
   {
      this.interfaces = new ArrayList<Interface>();
      this.hosts = new ArrayList<Host>();
      this.registries = new ArrayList<ServiceRegistry>();
      this.agents = new TreeMap<ID,Agent>();
      this.allowUpgradeFrom = new TreeSet<ID>();
      this.allowArchiveDomain = new TreeSet<String>();
      this.username = null;
      this.password = null;
   }
   
   public String getUsername() {
      return username;
   }
   
   public String getPassword() {
      return password;
   }
   
   public void setUsername(String username) {
      this.username = username;
   }
   
   public void setPassword(String password) {
      this.password = password;
   }
   
   public Set<ID> getUpgradesAllowedFrom() {
      return allowUpgradeFrom;
   }
   
   public Set<String> getAllowArchiveFrom() {
      return allowArchiveDomain;
   }
   
   
   public Host createHost(Element hostE)
   {
      String name = hostE.getAttributeValue("name");
      String address = hostE.getAttributeValue("address");
      String port = hostE.getAttributeValue("port");
      Host host = new Host(name,address,port==null ? -1 : Integer.parseInt(port));
      return host;
      
   }
   
   public ServiceRegistry createServiceRegistry(Element servicesE)
      throws IOException,XMLException
   {
      String href = servicesE.getAttributeValue("href");
      if (href!=null) {
         URI location = servicesE.getBaseURI().resolve(href);
         
         ServiceRegistry registry = new ServiceRegistry(href,location);
         registry.load();
         
         return registry;
      }
      return null;
      
   }
   
   public Agent createAgent(ID id)
   {
      return new Agent(id);
   }
   
   public Agent createAgent(Element agentE) 
      throws IOException,XMLException
   {
      String sid = agentE.getAttributeValue("id");
      if (sid==null) {
         throw new XMLException("Missing the 'id' attribute on "+AGENT);
      }
      
      ID id = new ID(sid);

      String password = agentE.getAttributeValue("password");
      id.setPassword(password);
      
      Agent agent = createAgent(id);

      if ("false".equals(agentE.getAttributeValue("allow-builtin"))) {
         agent.setAllowBuiltin(false);
      }
      
      Iterator<Element> brokerElements = agentE.getElementsByName(BROKER);
      while (brokerElements.hasNext()) {
         Element brokerE = brokerElements.next();
         String bid = brokerE.getAttributeValue("id");
         if (bid!=null) {
            agent.getBrokers().add(new ID(bid));
         }
      }
      
      Iterator<Element> hostElements = agentE.getElementsByName(HOST);
      while (hostElements.hasNext()) {
         Element hostE = hostElements.next();
         agent.getHosts().add(createHost(hostE));
      }
      
      Iterator<Element> servicesElements = agentE.getElementsByName(SERVICES);
      while (servicesElements.hasNext()) {
         Element servicesE = servicesElements.next();
         agent.getServiceRegistries().add(createServiceRegistry(servicesE));
      }
      
      ServiceRegistry local = agent.getLocalServices();
      Iterator<Element> serviceElements = agentE.getElementsByName(ServiceRegistry.SERVICE);
      while (serviceElements.hasNext()) {
         Element serviceE = serviceElements.next();
         Service service = ServiceRegistry.createService(serviceE);
         local.put(service.getName(), service);
      }

      Element forwardE = agentE.getFirstElementNamed(FORWARD);
      if (forwardE!=null) {
         String to = forwardE.getAttributeValue("to");
         if (to==null) {
            to = "/";
         }
         agent.forwardTarget = to;
      }

      return agent;
   }
   
   public URI getLocation() {
      return location;
   }
   
   public void setLocation(URI location) {
      this.location = location;
   }
   
   public void load(URI location)
      throws IOException,XMLException
   {
      this.location = location;
      DocumentLoader loader = new SAXDocumentLoader();
      Document doc = loader.load(location);
      Element top = doc.getDocumentElement();
      if (!top.getName().equals(SERVER)) {
         throw new XMLException("Expecting "+SERVER+" but found "+top.getName());
      }
      
      Element manager = top.getFirstElementNamed(MANAGER);
      if (manager!=null) {
         this.username = manager.getAttributeValue("username");
         this.password = manager.getAttributeValue("password");
         Iterator<Element> froms = manager.getElementsByName(ALLOW_FROM);
         while (froms.hasNext()) {
            String idS = froms.next().getAttributeValue("id");
            if (idS!=null) {
               allowUpgradeFrom.add(new ID(idS));
            }
         }
         Iterator<Element> domains = manager.getElementsByName(ALLOW_DOMAIN);
         while (domains.hasNext()) {
            String name = domains.next().getAttributeValue("name");
            if (name!=null) {
               allowArchiveDomain.add(name);
            }
         }
      }
      
      Element keyStoreE = top.getFirstElementNamed(KEYSTORE);
      if (keyStoreE!=null) {
         URI fileRef = keyStoreE.getBaseURI().resolve(keyStoreE.getAttributeValue("file"));
         this.keyStorePath = new File(fileRef.getSchemeSpecificPart());
         this.keyStorePassword = keyStoreE.getAttributeValue("password");
      }
      
      Iterator<Element> interfaceElements = top.getElementsByName(INTERFACE);
      while (interfaceElements.hasNext()) {
         Element interfaceE = interfaceElements.next();
         String addr = interfaceE.getAttributeValue("address");
         String portS = interfaceE.getAttributeValue("port");
         boolean secure = interfaceE.getAttributeValue("secure")==null || "true".equals(interfaceE.getAttributeValue("secure"));
         int port = portS==null ? (secure ? 443 : 80) : Integer.parseInt(portS);
         Interface iface = new Interface(addr,port,secure);
         interfaces.add(iface);
         
      }
      
      Iterator<Element> hostElements = top.getElementsByName(HOST);
      while (hostElements.hasNext()) {
         Element hostE = hostElements.next();
         hosts.add(createHost(hostE));
      }
      
      Iterator<Element> servicesElements = top.getElementsByName(SERVICES);
      while (servicesElements.hasNext()) {
         Element servicesE = servicesElements.next();
         registries.add(createServiceRegistry(servicesE));
      }
      
      Iterator<Element> agentElements = top.getElementsByName(AGENT);
      while (agentElements.hasNext()) {
         Element agentE = agentElements.next();
         Agent agent = createAgent(agentE);
         if (agents.get(agent.getId())!=null) {
            throw new XMLException("An agent with id "+agent.getId()+" already exists",(Location)agentE);
         }
         agents.put(agent.getId(),agent);
      }
      
      Iterator<Element> upgradeElements = top.getElementsByName(UPGRADE);
      while (upgradeElements.hasNext()) {
         Element upgradeE = upgradeElements.next();
         Iterator<Element> fromList = upgradeE.getElementsByName(FROM);
         while (fromList.hasNext()) {
            String idS = fromList.next().getAttributeValue("id");
            if (idS!=null) {
               allowUpgradeFrom.add(new ID(idS));
            }
         }
         Iterator<Element> domainList = upgradeE.getElementsByName(DOMAIN);
         while (domainList.hasNext()) {
            String name = domainList.next().getAttributeValue("name");
            if (name!=null) {
               name = name.trim();
               if (name.length()!=0) {
                  allowArchiveDomain.add(name);
               }
            }
         }
         
         
      }
   }
   
   public void save() 
      throws IOException,XMLException
   {
      if (!location.getScheme().equals("file")) {
         throw new IOException("Cannot save to non-file configuration location: "+location);
      }
      File confFile = new File(location.getSchemeSpecificPart());
      Writer out = new OutputStreamWriter(new FileOutputStream(confFile),"UTF-8");
      save(new WriterItemDestination(out,"UTF-8"));
      out.close();
   }
   
   public void save(ItemDestination dest)
      throws XMLException
   {
      ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
      dest.send(constructor.createDocument(location));
      dest.send(constructor.createElement(SERVER));
      if (username!=null || allowUpgradeFrom.size()>0 || allowArchiveDomain.size()>0) {
         dest.send(constructor.createCharacters("\n"));
         Element manager = constructor.createElement(MANAGER);
         if (username!=null) {
            manager.setAttributeValue("username", username);
            manager.setAttributeValue("password", password);
         }
         dest.send(manager);
         for (ID id : allowUpgradeFrom) {
            dest.send(constructor.createCharacters("\n"));
            Element allowFrom = constructor.createElement(ALLOW_FROM);
            allowFrom.setAttributeValue("id",id.toString());
            dest.send(allowFrom);
            dest.send(constructor.createElementEnd(ALLOW_FROM));
         }
         for (String name : allowArchiveDomain) {
            dest.send(constructor.createCharacters("\n"));
            Element allowDomain = constructor.createElement(ALLOW_DOMAIN);
            allowDomain.setAttributeValue("name",name);
            dest.send(allowDomain);
            dest.send(constructor.createElementEnd(ALLOW_DOMAIN));
         }
         if (allowUpgradeFrom.size()>0 || allowArchiveDomain.size()>0) {
            dest.send(constructor.createCharacters("\n"));
         }
         dest.send(constructor.createElementEnd(MANAGER));
      }
      if (keyStorePath!=null) {
         dest.send(constructor.createCharacters("\n"));
         Element keystore = constructor.createElement(KEYSTORE);
         keystore.setAttributeValue("file",location.relativize(keyStorePath.toURI()).toString());
         if (keyStorePassword!=null) {
            keystore.setAttributeValue("passsword",keyStorePassword);
         }
         dest.send(keystore);
         dest.send(constructor.createElementEnd(KEYSTORE));
      }
      for (Interface iface : interfaces) {
         dest.send(constructor.createCharacters("\n"));
         Element ifaceE = constructor.createElement(INTERFACE);
         ifaceE.setAttributeValue("address", iface.getAddress());
         if (iface.isSecure()) {
            ifaceE.setAttributeValue("secure","true");
            if (iface.getPort()!=443) {
               ifaceE.setAttributeValue("port",Integer.toString(iface.getPort()));
            }
         } else {
            ifaceE.setAttributeValue("secure","false");
            if (iface.getPort()!=80) {
               ifaceE.setAttributeValue("port",Integer.toString(iface.getPort()));
            }
         }
         dest.send(ifaceE);
         dest.send(constructor.createElementEnd(INTERFACE));
      }
      
      for (Host host : hosts) {
         dest.send(constructor.createCharacters("\n"));
         Element hostE = constructor.createElement(HOST);
         hostE.setAttributeValue("name", host.getName());
         if (host.getAddress()!=null) {
            hostE.setAttributeValue("address",host.getAddress());
         }
         if (host.getPort()>0) {
            hostE.setAttributeValue("port",Integer.toString(host.getPort()));
         }
         dest.send(hostE);
         dest.send(constructor.createElementEnd(HOST));
      }
      
      for (Agent agent : agents.values()) {
         dest.send(constructor.createCharacters("\n"));
         Element agentE = constructor.createElement(AGENT);
         agentE.setAttributeValue("id", agent.getId().toString());
         agentE.setAttributeValue("password", agent.getId().getPassword());
         dest.send(agentE);
         for (Host host : agent.getHosts()) {
            dest.send(constructor.createCharacters("\n"));
            Element hostE = constructor.createElement(HOST);
            hostE.setAttributeValue("name", host.getName());
            if (host.getAddress()!=null) {
               hostE.setAttributeValue("address",host.getAddress());
            }
            if (host.getPort()>0) {
               hostE.setAttributeValue("port",Integer.toString(host.getPort()));
            }
            dest.send(hostE);
            dest.send(constructor.createElementEnd(HOST));
         }
         for (Service local : agent.getLocalServices().values()) {
            dest.send(constructor.createCharacters("\n"));
            local.save(location,constructor,dest);
         }
         for (ServiceRegistry registry : agent.getServiceRegistries()) {
            if (registry.getLocation()!=null) {
               dest.send(constructor.createCharacters("\n"));
               Element servicesE = constructor.createElement(SERVICES);
               servicesE.setAttributeValue("href",registry.getLocationReference());
               dest.send(servicesE);
               dest.send(constructor.createElementEnd(SERVICES));
            }
         }
         dest.send(constructor.createCharacters("\n"));
         dest.send(constructor.createElementEnd(AGENT));
      }
      dest.send(constructor.createCharacters("\n"));
      dest.send(constructor.createElementEnd(SERVER));
      dest.send(constructor.createDocumentEnd());
   }
   
   public File getKeyStorePath()
   {
      return keyStorePath;
   }
   
   public String getKeyStorePassword()
   {
      return keyStorePassword;
   }
   
   public List<Interface> getInterfaces() {
      return interfaces;
   }

   public void setKeyStorePath(File keyStorePath)
   {
      this.keyStorePath = keyStorePath;
   }

   public void setKeyStorePassword(String keyStorePassword)
   {
      this.keyStorePassword = keyStorePassword;
   }
   
   public List<Host> getHosts() {
      return hosts;
   }
   
   public List<ServiceRegistry> getServiceRegistries() {
      return registries;
   }
   
   public Map<ID,Agent> getAgents() {
      return agents;
   }

}